﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Shlook.Common;
using System.ServiceModel;

namespace Shlook.Client
{
    public class UserStatusChangedEventArgs : EventArgs
    {
        public User User { get; set; }
    }
    public class MessageArrivedEventArgs : EventArgs
    {
        public User From { get; set; }
        public string Message { get; set; }
    }
    public class UserConnectedEventArgs : EventArgs
    {
        public User User { get; set; }
    }
    public class UserDisconnectedEventArgs : EventArgs
    {
        public string UserId { get; set; }
    }
    public class ClientErrorEventArgs : EventArgs
    {
        public string Error { get; set; }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ChatClient : IChatClient
    {
        private const int DefaultRetryCount = 2;
        private static readonly User[] EmptyUserArray = new User[0];

        private IChatServer _server;
        private User _user;
        private ServiceHost _host;
        private string _serverURL;

        public ChatClient(string userId, string displayName, string status)
        {
            _user = new User { Id = userId, DisplayName = displayName, Status = status };
        }

        public void Connect(string serverURL, string clientURL)
        {
            _serverURL = serverURL;
            _user.URL = clientURL;

            //The connect path is critical and we don't want to retry automatically,
            //as that would bring us into an infinite loop (Reconnect() calls Connect()).
            try
            {
                _host = new ServiceHost(this);
                _host.AddServiceEndpoint(typeof(IChatClient), Bindings.Default, clientURL);
                _host.Open();

                _server = ChannelFactory<IChatServer>.CreateChannel(Bindings.Default, new EndpointAddress(_serverURL));
                _server.Connect(_user);
            }
            catch (Exception e)
            {
                ClientError(this, new ClientErrorEventArgs { Error=e.ToString() });
            }
        }

        public void Disconnect()
        {
            try
            {
                _server.Disconnect(_user.Id);
                ((ICommunicationObject)_server).Close();
                _host.Close(TimeSpan.FromSeconds(1));
            }
            catch (Exception /*e*/)
            {
                //We don't particularly care about errors in the disconnect path
                /*ClientError(this, new ClientErrorEventArgs { Error = e.ToString() });*/
            }
        }

        public User[] GetUsers()
        {
            User[] users = EmptyUserArray;
            PerformWithRetries(() => users = _server.GetUsers(), DefaultRetryCount, true);
            return users;
        }

        public void ChangeStatus(string newStatus)
        {
            PerformWithRetries(() => _server.ChangeStatus(_user.Id, newStatus), DefaultRetryCount, true);
        }

        public void SendMessage(string[] toUserIds, string message)
        {
            ChatMessage[] messages = Array.ConvertAll<string, ChatMessage>(toUserIds, u => new ChatMessage { FromUser = _user.Id, ToUser = u, Message = message });
            PerformWithRetries(() => _server.SendMessage(messages), DefaultRetryCount, true);
        }

        public void SendMessage(string toUserId, string message)
        {
            SendMessage(new string[] { toUserId }, message);
        }

        private void Reconnect()
        {
            Connect(_serverURL, _user.URL);
        }

        private void PerformWithRetries(Action action, int retries, bool reconnect)
        {
            Exception lastException = null;
            for (int i = 0; i < retries; ++i)
            {
                try
                {
                    action();
                    return;
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    if (reconnect)
                    {
                        Reconnect();
                    }
                }
            }
            ClientError(this, new ClientErrorEventArgs { Error = lastException.ToString() });
        }

        public event EventHandler<UserStatusChangedEventArgs> StatusChanged = delegate { };
        public event EventHandler<MessageArrivedEventArgs> MessageArrived = delegate { };
        public event EventHandler<UserConnectedEventArgs> UserAdded = delegate { };
        public event EventHandler<UserDisconnectedEventArgs> UserRemoved = delegate { };
        public event EventHandler<ClientErrorEventArgs> ClientError = delegate { };

        void IChatClient.NewMessage(User from, string message)
        {
            MessageArrived(this, new MessageArrivedEventArgs { From = from, Message = message });
        }

        void IChatClient.UserStatusChanged(User user)
        {
            StatusChanged(this, new UserStatusChangedEventArgs { User = user });
        }

        void IChatClient.UserConnected(User user)
        {
            UserAdded(this, new UserConnectedEventArgs { User = user });
        }

        void IChatClient.UserDisconnected(string userId)
        {
            UserRemoved(this, new UserDisconnectedEventArgs { UserId = userId });
        }

        void IChatClient.KeepAlive()
        {
        }
    }
}
